/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning
  IndexCursor cursor;
  RC     rc;
  int    key;     
  string value;
  int    count;
  int    diff;
  bool indexFlag = false;
  bool keyFlag = false;

  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }
  
  rid.pid = rid.sid = 0;
  
  
	BTreeIndex index;
  if ((rc = index.open(table+".idx", 'r')) == 0) {
	indexFlag = true;
	
	}
  else
  {
	index.close();
  }
  
  for (unsigned c = 0; c < cond.size(); c++) {
	if(cond[c].attr == 1)
		keyFlag = true;
  }
  
  // scan the table file from the beginning
  
  count = 0;
  
  if(keyFlag == false || indexFlag == false)
  {
	  while (rid < rf.endRid()) {
		// read the tuple
		if ((rc = rf.read(rid, key, value)) < 0) {
		  fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
		  goto exit_select;
		}
		// check the conditions on the tuple
		for (unsigned i = 0; i < cond.size(); i++) {
		  // compute the difference between the tuple value and the condition value
		  switch (cond[i].attr) {
		  case 1:
		diff = key - atoi(cond[i].value);
		
		break;
		  case 2:
		diff = strcmp(value.c_str(), cond[i].value);
		break;
		  }
		  
		  
		  // skip the tuple if any condition is not met
		  switch (cond[i].comp) {
		  case SelCond::EQ:
		if (diff != 0) goto next_tuple;
		break;
		  case SelCond::NE:
		if (diff == 0) goto next_tuple;
		break;
		  case SelCond::GT:
		if (diff <= 0) goto next_tuple;
		break;
		  case SelCond::LT:
		if (diff >= 0) goto next_tuple;
		break;
		  case SelCond::GE:
		if (diff < 0) goto next_tuple;
		break;
		  case SelCond::LE:
		if (diff > 0) goto next_tuple;
		break;
		  }
		}

		// the condition is met for the tuple. 
		// increase matching tuple counter
		count++;

		// print the tuple 
		switch (attr) {
		case 1:  // SELECT key
		  fprintf(stdout, "%d\n", key);
		  break;
		case 2:  // SELECT value
		  fprintf(stdout, "%s\n", value.c_str());
		  break;
		case 3:  // SELECT *
		  fprintf(stdout, "%d '%s'\n", key, value.c_str());
		  break;
		}

		// move to the next tuple
		next_tuple:
		{
			++rid;
		}
	  }

	  // print matching tuple count if "select count(*)"
	  if (attr == 4) {
		fprintf(stdout, "%d\n", count);
	  }
	  rc = 0;

	  // close the table file and return
	  exit_select:
	  rf.close();
	  return rc;
  }
  else
  {
	bool endOfTree = false;
	//find range of keys to use
	bool endDef = false; //start and end key have/haven't been defined
	bool eqCond = false;
	bool ltCheck = false;
	int startKey = 0;
	int endKey;
	
	
	/*
	for (unsigned a = 0; a < cond.size(); a++) {
		if(cond[a].attr == 1)
		{
			switch (cond[a].comp) {
			case SelCond::EQ:
				if(eqCond == false)
				{
					startKey = endKey = atoi(cond[a].value);
					startDef = true;
					endDef = true;
					eqCond = true;
					index.locate(startKey, cursor);
					index.readForward(cursor, key, rid);
				}
				else
				{
					if(atoi(cond[a].value) != startKey)
						goto exit_select2;
				}
				break;
			case SelCond::NE:
				break;
			case SelCond::GE:
				/*if(eqCond == false)
				{
					if(startDef == false || startKey < atoi(cond[a].value))
					{
						startKey = atoi(cond[a].value);
						index.locate(startKey, cursor);
						index.readForward(cursor, key, rid);
						startDef = true;
					}
				}
				break;
			case SelCond::GT:
				/*if(eqCond == false)
				{
					if(startDef == false || startKey < atoi(cond[a].value))
					{
						startKey = atoi(cond[a].value);
						index.locate(startKey, cursor);
						index.readForward(cursor, key, rid);
						startDef = true;
						index.readForward(cursor, startKey, rid);
					}
					
				}
				break;
			case SelCond::LT:
				//ltCheck = true;
			case SelCond::LE:
				/*if(eqCond == false)
				{
					if(endDef == false || endKey > atoi(cond[a].value))
					{
						endKey = atoi(cond[a].value);
						//index.locate(endKey, cursor);
						//index.readForward(cursor, key, rid);
						endDef = true;
					}
				}
				break;
			}	
		}*/
		
		/*if(startDef == true && endDef == true && startKey > endKey)
		{
			goto exit_select2;
		}
	  }*/
	  
	index.locate(startKey, cursor); //set first index to be first key
	
  	
	RecordId temp;
	index.read(cursor, startKey, rid);
	
	while (endOfTree == false) {
		temp = rid;
		//temp.sid = rid.sid;
		//temp.pid = rid.pid;
		RC check_next_tuple = index.readForward(cursor, key, rid);
		//rid.sid = temp.sid;
		//rid.pid = temp.pid;
		// read the tuple
		if ((rc = rf.read(temp, key, value)) < 0) {
		  fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
		  goto exit_select2;
		}
		
		// check the conditions on the tuple
		for (unsigned i = 0; i < cond.size(); i++) {
		  // compute the difference between the tuple value and the condition value
		  switch (cond[i].attr) {
		  case 1:
		diff = key - atoi(cond[i].value);		
		break;
		  case 2:
		diff = strcmp(value.c_str(), cond[i].value);
		break;
		  }

		  
		  
		  
		  // skip the tuple if any condition is not met
		  switch (cond[i].comp) {
		  case SelCond::EQ:
		if (diff != 0) goto next_tuple2;
		break;
		  case SelCond::NE:
		if (diff == 0) goto next_tuple2;
		break;
		  case SelCond::GT:
		if (diff <= 0) goto next_tuple2;
		break;
		  case SelCond::LT:
		if (diff >= 0) goto next_tuple2;
		break;
		  case SelCond::GE:
		if (diff < 0) goto next_tuple2;
		break;
		  case SelCond::LE:
		if (diff > 0) goto next_tuple2;
		break;
		  }
		}

		// the condition is met for the tuple. 
		// increase matching tuple counter
		count++;

		// print the tuple 
		switch (attr) {
		case 1:  // SELECT key
		  fprintf(stdout, "%d\n", key);
		  break;
		case 2:  // SELECT value
		  fprintf(stdout, "%s\n", value.c_str());
		  break;
		case 3:  // SELECT *
		  fprintf(stdout, "%d '%s'\n", key, value.c_str());
		  break;
		}

		// move to the next tuple
		next_tuple2:
		{
			if(eqCond == true)
				endOfTree = true;
				
				
			if(endDef == true)
				if(key == endKey)
					endOfTree = true;
					
			if(check_next_tuple < 0)
				endOfTree = true;
				
			if(key == endKey && ltCheck == true)
				endOfTree = true;
				
			//++rid;
		}
	  }

	  // print matching tuple count if "select count(*)"
	  if (attr == 4) {
		fprintf(stdout, "%d\n", count);
	  }
	  rc = 0;

	  // close the table file and return
	  exit_select2:
	  {
		rf.close();
		index.close();
	  }
	  return rc;

  
  }
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
	string table_name = table + ".tbl";
	RecordFile rf;
	rf.open(table_name, 'w');
	string fileToRead = loadfile;
	RC rc;
	if (index == true)	//should never happen since index is false
	{
		BTreeIndex treeindex;
		string name = table + ".idx";
		if(rc = treeindex.open(name, 'w') < 0)
		{
			return rc;
		}
		fstream filestr;
		filestr.open (fileToRead.c_str(), fstream::in | fstream::out); //allow only output since we're just reading the file
		string file_line;
		RecordId rid;
		while (getline(filestr, file_line) )
		{
			int key; //key from key-value pair
			string value; //value from key-value pair
			parseLoadLine(file_line, key, value);
			rid = rf.endRid();
			treeindex.insert(key, rid);
			
			rf.append(key, value, rid);
		}
		treeindex.close();
		rf.close();
		filestr.close(); //close the file
		
	}
	else
	{	
		fstream filestr;
		filestr.open (fileToRead.c_str(), fstream::in | fstream::out); //allow only output since we're just reading the file
		string file_line;
		while (getline(filestr, file_line) )
		{
			int key; //key from key-value pair
			string value; //value from key-value pair
			parseLoadLine(file_line, key, value);
			RecordId rid = rf.endRid();
			rf.open(table_name, 'w');
			rf.append(key, value, rid);
			rf.close();
		}
		filestr.close(); //close the file
	}
	return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
